const crypto = require('crypto')

const JD_API_URL = 'https://api.jd.com/routerjson'

/**
 * 数字补零
 * @param {number} num
 * @returns {string}
 */
function pad(num) {
  return num < 10 ? `0${num}` : `${num}`
}

/**
 * 统一格式化时间戳
 * @param {number | string | Date} [input]
 * @returns {string}
 */
function formatTimestamp(input) {
  if (!input) {
    input = Date.now()
  }
  const date = input instanceof Date ? input : new Date(input)
  if (Number.isNaN(date.getTime())) {
    throw new Error('timestamp无效')
  }
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}

/**
 * 将值规范化为字符串，方便签名拼接
 * @param {any} value
 * @returns {string}
 */
function normalizeValue(value) {
  if (typeof value === 'object' && value !== null) {
    return JSON.stringify(value)
  }
  return String(value)
}

/**
 * 全局配置存储
 */
let globalConfig = {
  appKey: null,
  appSecret: null
}

/**
 * 设置全局配置
 * @param {object} config
 * @param {string} config.appKey
 * @param {string} config.appSecret
 */
function setGlobalConfig(config) {
  if (config.appKey) globalConfig.appKey = config.appKey
  if (config.appSecret) globalConfig.appSecret = config.appSecret
}

/**
 * 获取全局配置
 */
function getGlobalConfig() {
  return { ...globalConfig }
}

/**
 * 标准化输入参数结构
 * @param {object} payload 可以是新格式或旧格式
 * @returns {{auth: object, query: object, options: object}}
 */
function normalizePayload(payload) {
  if (!payload) {
    throw new Error('参数不能为空')
  }

  // 如果已经是新格式
  if (payload.auth || payload.query || payload.options) {
    const auth = payload.auth || {}
    const query = payload.query || {}
    const options = payload.options || {}

    return {
      auth: {
        appKey: auth.appKey || globalConfig.appKey,
        appSecret: auth.appSecret || globalConfig.appSecret,
        accessToken: auth.accessToken
      },
      query,
      options
    }
  }

  // 兼容旧格式
  const {
    appKey,
    appSecret,
    accessToken,
    timestamp,
    version,
    format,
    signMethod,
    persistence,
    ...restQuery
  } = payload

  return {
    auth: {
      appKey: appKey || globalConfig.appKey,
      appSecret: appSecret || globalConfig.appSecret,
      accessToken
    },
    query: restQuery,
    options: {
      timestamp,
      version,
      format,
      signMethod,
      db: persistence
    }
  }
}

/**
 * 构造京东联盟接口的系统参数
 * @param {object} payload
 * @returns {{params: Record<string, any>, appSecret: string}}
 */
function buildSignParams(payload) {
  const {
    appKey,
    appSecret,
    method,
    businessParams,
    systemParams = {},
    timestamp,
    version = '1.0',
    format = 'json',
    signMethod = 'md5',
    accessToken
  } = payload || {}

  if (!appKey) {
    throw new Error('appKey不能为空')
  }
  if (!appSecret) {
    throw new Error('appSecret不能为空')
  }
  if (!method) {
    throw new Error('method不能为空')
  }

  const params = {
    app_key: appKey,
    method,
    format,
    v: version,
    sign_method: signMethod,
    timestamp: formatTimestamp(timestamp),
    ...systemParams
  }

  if (accessToken) {
    params.access_token = accessToken
  }

  if (businessParams !== undefined) {
    params['360buy_param_json'] =
      typeof businessParams === 'string' ? businessParams : JSON.stringify(businessParams || {})
  }

  return { params, appSecret }
}

/**
 * 基于系统参数生成京东签名
 * @param {string} appSecret
 * @param {Record<string, any>} params
 * @returns {{sign: string, rawStr: string, sortedKeys: string[]}}
 */
function buildJdSign(appSecret, params) {
  const sortedKeys = Object.keys(params)
    .filter((key) => params[key] !== undefined && params[key] !== null && params[key] !== '')
    .sort()
  const joined = sortedKeys.map((key) => `${key}${normalizeValue(params[key])}`).join('')
  const rawStr = `${appSecret}${joined}${appSecret}`
  const sign = crypto.createHash('md5').update(rawStr, 'utf8').digest('hex').toUpperCase()
  return {
    sign,
    rawStr,
    sortedKeys
  }
}

/**
 * 解析京东API返回的 queryResult 字段
 * @param {any} response
 * @param {string} [responseField]
 * @returns {Record<string, any> | undefined}
 */
function parseJdQueryResult(response, responseField = 'jd_union_open_goods_rank_query_responce') {
  const rawRes = response && response[responseField]
  if (!rawRes || !rawRes.queryResult) {
    return undefined
  }
  const { queryResult } = rawRes
  if (typeof queryResult === 'object') {
    return queryResult
  }
  if (typeof queryResult === 'string') {
    try {
      const parsed = JSON.parse(queryResult)
      return parsed
    } catch (error) {
      return {
        code: -1,
        message: 'queryResult解析失败',
        raw: queryResult
      }
    }
  }
  return {
    code: -1,
    message: '未知的queryResult类型',
    raw: queryResult
  }
}

/**
 * 标准化数据库配置
 * @param {string | object} dbConfig
 * @returns {object | null}
 */
function normalizeDbConfig(dbConfig) {
  if (!dbConfig) return null

  // 如果是字符串，作为集合名
  if (typeof dbConfig === 'string') {
    return {
      collection: dbConfig,
      uniqueKeys: ['itemId', 'mainSkuId', 'skuName']
    }
  }

  // 如果是对象
  if (typeof dbConfig === 'object') {
    // 兼容旧格式 {enable, collection, extraDocFields}
    if (dbConfig.enable === false) return null

    return {
      collection: dbConfig.collection || dbConfig.name,
      uniqueKeys:
        dbConfig.uniqueKeys || dbConfig.uniqueKey
          ? [dbConfig.uniqueKey]
          : ['itemId', 'mainSkuId', 'skuName'],
      extraFields: dbConfig.extraFields || dbConfig.extraDocFields
    }
  }

  return null
}

/**
 * 数据落库，支持多字段唯一性校验
 * @param {object} options
 * @param {string} options.collectionName
 * @param {Array<any>} options.list
 * @param {Record<string, any>} [options.extraDocFields]
 * @param {(item: any) => Record<string, any>} [options.transform]
 * @param {string} [options.uniqueKey]
 * @param {string[]} [options.uniqueKeys]
 */
async function persistDocs(options) {
  const { collectionName, list, extraDocFields, transform, uniqueKey, uniqueKeys } = options || {}

  if (!collectionName) {
    throw new Error('数据库集合名不能为空')
  }
  if (!Array.isArray(list) || list.length === 0) {
    return {
      inserted: 0,
      updated: 0
    }
  }

  const db = uniCloud.database()
  const collection = db.collection(collectionName)
  const dbCmd = db.command
  const now = Date.now()
  const docs = list.map((item) => {
    const base = typeof transform === 'function' ? transform(item) : item
    return {
      ...base,
      ...(extraDocFields || {})
    }
  })

  const multiUniqueKeys = Array.isArray(uniqueKeys)
    ? uniqueKeys.filter((key) => typeof key === 'string' && key.length > 0)
    : []

  if (uniqueKey && multiUniqueKeys.length === 0) {
    const uniqueValues = Array.from(
      new Set(
        docs
          .map((doc) => doc && doc[uniqueKey])
          .filter((value) => value !== undefined && value !== null && value !== '')
      )
    )

    let existingSet = new Set()
    if (uniqueValues.length > 0) {
      const fieldSelector = {
        [uniqueKey]: 1
      }
      const { data: existingDocs } = await collection
        .where({
          [uniqueKey]: dbCmd.in(uniqueValues)
        })
        .field(fieldSelector)
        .get()
      existingSet = new Set((existingDocs || []).map((doc) => doc[uniqueKey]))
    }

    const toInsert = []
    const toUpdate = []
    for (const doc of docs) {
      const keyValue = doc ? doc[uniqueKey] : undefined
      if (keyValue === undefined || keyValue === null || keyValue === '') {
        toInsert.push(doc)
        continue
      }
      if (existingSet.has(keyValue)) {
        toUpdate.push(doc)
      } else {
        toInsert.push(doc)
      }
    }

    let inserted = 0
    if (toInsert.length > 0) {
      const docsToInsert = toInsert.map((doc) => ({
        ...doc,
        createdAt: doc?.createdAt || now,
        updatedAt: now
      }))
      const insertRes = await collection.add(docsToInsert)
      inserted = insertRes.inserted || docsToInsert.length
    }

    let updated = 0
    for (const doc of toUpdate) {
      const payload = {
        ...doc,
        updatedAt: now
      }
      delete payload.createdAt
      await collection
        .where({
          [uniqueKey]: doc[uniqueKey]
        })
        .update(payload)
      updated += 1
    }

    return {
      inserted,
      updated
    }
  }

  if (multiUniqueKeys.length > 0) {
    const fieldValueMaps = new Map()
    for (const field of multiUniqueKeys) {
      const values = Array.from(
        new Set(
          docs
            .map((doc) => doc && doc[field])
            .filter((value) => value !== undefined && value !== null && value !== '')
        )
      )
      if (values.length === 0) {
        continue
      }
      const { data: existingDocs } = await collection
        .where({
          [field]: dbCmd.in(values)
        })
        .field({
          _id: 1,
          [field]: 1,
          itemId: 1,
          mainSkuId: 1,
          skuName: 1
        })
        .get()
      for (const doc of existingDocs || []) {
        const mapKey = `${field}:${doc[field]}`
        if (!fieldValueMaps.has(mapKey)) {
          fieldValueMaps.set(mapKey, doc)
        }
      }
    }

    const toInsert = []
    const toUpdate = []
    for (const doc of docs) {
      let matched
      for (const field of multiUniqueKeys) {
        const value = doc ? doc[field] : undefined
        if (value === undefined || value === null || value === '') {
          continue
        }
        const mapKey = `${field}:${value}`
        if (fieldValueMaps.has(mapKey)) {
          matched = fieldValueMaps.get(mapKey)
          break
        }
      }
      if (matched) {
        toUpdate.push({
          doc,
          targetId: matched._id
        })
      } else {
        toInsert.push(doc)
      }
    }

    let inserted = 0
    if (toInsert.length > 0) {
      const docsToInsert = toInsert.map((doc) => ({
        ...doc,
        createdAt: doc?.createdAt || now,
        updatedAt: now
      }))
      const insertRes = await collection.add(docsToInsert)
      inserted = insertRes.inserted || docsToInsert.length
    }

    let updated = 0
    for (const item of toUpdate) {
      const payload = {
        ...item.doc,
        updatedAt: now
      }
      delete payload.createdAt
      await collection.doc(item.targetId).update(payload)
      updated += 1
    }

    return {
      inserted,
      updated
    }
  }

  const docsToInsert = docs.map((doc) => ({
    ...doc,
    createdAt: doc?.createdAt || now,
    updatedAt: now
  }))
  const insertRes = await collection.add(docsToInsert)
  return {
    inserted: insertRes.inserted || docsToInsert.length,
    updated: 0
  }
}

/**
 * 将榜单商品映射为便于落库的扁平结构
 * @param {Record<string, any>} item
 * @returns {Record<string, any>}
 */
function resolveImageList(item = {}) {
  const imageInfo = item.imageInfo || {}
  if (Array.isArray(imageInfo.imageList)) {
    return imageInfo.imageList.map((img) => img && img.url).filter(Boolean)
  }
  if (Array.isArray(item.imgList)) {
    return item.imgList.filter(Boolean)
  }
  return []
}

function mapRankGoodsDoc(item = {}) {
  const priceInfo = item.priceInfo || {}
  const commissionInfo = item.commissionInfo || {}
  const purchasePriceInfo = item.purchasePriceInfo || {}
  const categoryInfo = item.categoryInfo || {}
  const shopInfo = item.shopInfo || {}
  const resourceInfo = item.resourceInfo || {}
  const imageList = resolveImageList(item)
  const videoList = Array.isArray(item.videoInfo?.videoList) ? item.videoInfo.videoList : []
  return {
    itemId: item.itemId,
    mainSkuId: item.mainSkuId,
    spuid: item.spuid,
    skuName: item.skuName,
    owner: item.owner,
    brandCode: item.brandCode,
    brandName: item.brandName,
    isHot: item.isHot,
    isOversea: item.isOversea,
    coverImage: item.imageUrl || imageList[0] || item.whiteImage,
    imageList,
    videoList,
    skuTagList: item.skuTagList || [],
    comments: item.comments,
    inOrderCount30Days: item.inOrderCount30Days,
    inOrderCount30DaysSku: item.inOrderCount30DaysSku,
    goodComments: item.goodComments,
    goodCommentsShare: item.goodCommentsShare,
    wlprice: item.wlprice,
    price: priceInfo.price,
    lowestPrice: priceInfo.lowestPrice,
    lowestCouponPrice: priceInfo.lowestCouponPrice,
    commission: commissionInfo.commission ?? item.commission,
    commissionShare: commissionInfo.commissionShare ?? item.commissionShare,
    couponCommission: commissionInfo.couponCommission,
    plusCommissionShare: commissionInfo.plusCommissionShare,
    commissionStartTime: commissionInfo.startTime,
    commissionEndTime: commissionInfo.endTime,
    couponList: Array.isArray(item.couponInfo?.couponList) ? item.couponInfo.couponList : [],
    purchasePrice: purchasePriceInfo.purchasePrice,
    purchaseThresholdPrice: purchasePriceInfo.thresholdPrice,
    purchaseBasisPriceType: purchasePriceInfo.basisPriceType,
    purchaseCoupons: Array.isArray(purchasePriceInfo.couponList)
      ? purchasePriceInfo.couponList
      : [],
    category1Id: categoryInfo.cid1,
    category1Name: categoryInfo.cid1Name,
    category2Id: categoryInfo.cid2,
    category2Name: categoryInfo.cid2Name,
    category3Id: categoryInfo.cid3,
    category3Name: categoryInfo.cid3Name,
    shopId: shopInfo.shopId,
    shopName: shopInfo.shopName,
    shopLevel: shopInfo.shopLevel,
    shopLabel: shopInfo.shopLabel,
    shopScoreRankRate: shopInfo.scoreRankRate,
    shopAfterServiceScore: shopInfo.afterServiceScore,
    shopUserEvaluateScore: shopInfo.userEvaluateScore,
    shopLogisticsScore: shopInfo.logisticsLvyueScore,
    shopAfsGrade: shopInfo.afsFactorScoreRankGrade,
    shopCommentGrade: shopInfo.commentFactorScoreRankGrade,
    shopLogisticsGrade: shopInfo.logisticsFactorScoreRankGrade,
    materialUrl: item.materialUrl,
    resourceEliteId: resourceInfo.eliteId,
    resourceEliteName: resourceInfo.eliteName,
    pinGouInfo: item.pinGouInfo,
    forbidTypes: item.forbidTypes || [],
    deliveryType: item.deliveryType
  }
}

/**
 * 将大字段商品映射为便于落库的扁平结构
 * @param {Record<string, any>} item
 * @returns {Record<string, any>}
 */
function mapBigfieldGoodsDoc(item = {}) {
  const baseBigFieldInfo = item.baseBigFieldInfo || {}
  const categoryInfo = item.categoryInfo || {}
  const imageInfo = item.imageInfo || {}
  const detailImages = Array.isArray(item.detailImages)
    ? item.detailImages
    : typeof item.detailImages === 'string'
      ? item.detailImages
          .split(',')
          .map((url) => url.trim())
          .filter(Boolean)
      : []
  const imageList = Array.isArray(imageInfo.imageList)
    ? imageInfo.imageList.map((img) => img && img.url).filter(Boolean)
    : []
  return {
    itemId: item.itemId,
    mainSkuId: item.mainSkuId,
    productId: item.productId,
    skuName: item.skuName,
    owner: item.owner,
    skuStatus: item.skuStatus,
    wdis: baseBigFieldInfo.wdis,
    brandCode: item.brandCode,
    brandName: item.brandName,
    category1Id: categoryInfo.cid1,
    category1Name: categoryInfo.cid1Name,
    category2Id: categoryInfo.cid2,
    category2Name: categoryInfo.cid2Name,
    category3Id: categoryInfo.cid3,
    category3Name: categoryInfo.cid3Name,
    detailImages,
    imageList
  }
}

/**
 * 合并榜单与大字段的信息
 * @param {Record<string, any>} item
 * @returns {Record<string, any>}
 */
function mapMergedGoodsDoc(item = {}) {
  const baseDoc = mapRankGoodsDoc(item)
  const bigfield = item.bigfield || {}
  const detailDoc =
    bigfield && Object.keys(bigfield).length > 0 ? mapBigfieldGoodsDoc(bigfield) : {}
  return {
    ...baseDoc,
    ...(detailDoc || {}),
    mergedAt: Date.now()
  }
}

/**
 * 提取 queryResult 的关键信息
 * @param {Record<string, any>} result
 */
function buildQueryResultMeta(result) {
  if (!result || typeof result !== 'object') {
    return undefined
  }
  const meta = {
    code: result.code,
    message: result.message,
    requestId: result.requestId,
    totalCount: result.totalCount
  }
  return Object.keys(meta).some((key) => meta[key] !== undefined) ? meta : undefined
}

/**
 * 将数组拆分为固定大小的子数组
 * @param {Array<any>} list
 * @param {number} size
 * @returns {Array<any[]>}
 */
function chunkArray(list, size) {
  if (!Array.isArray(list) || size <= 0) {
    return []
  }
  const chunks = []
  for (let i = 0; i < list.length; i += size) {
    chunks.push(list.slice(i, i + size))
  }
  return chunks
}

module.exports = {
  JD_API_URL,
  setGlobalConfig,
  getGlobalConfig,
  normalizePayload,
  normalizeDbConfig,
  buildSignParams,
  buildJdSign,
  parseJdQueryResult,
  persistDocs,
  mapRankGoodsDoc,
  mapBigfieldGoodsDoc,
  mapMergedGoodsDoc,
  buildQueryResultMeta,
  chunkArray
}
